package com.hw.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hw.event.DeviceEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class ChatingHandler extends TextWebSocketHandler {
    @Autowired
    private ObjectMapper objectMapper;
    //用于保存所有会话的集合,key 是唯一表示, value 是会话
    private static Map<String, WebSocketSession> allClients;
    @Autowired
    private ApplicationContext context;

    static {
        allClients = new ConcurrentHashMap<>();//用于保存会话的集合,使用了线程安全的集合,因为我们是单例对象,在并发的情况下会出现线程安全的问题
    }

    /**
     * 当客户端和服务端建立连接的时候,就相当于你登录 QQ 成功,服务器必须保存你的会话数据,不然给你发送数据的时候就找不到你了,每个建立连接的都需要保存
     *
     * @param session 当前正在会话的人
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //保存会话
        //1 保存在什么地方,因为会有很多人建立连接,所以一定要用集合,无非就是键值对类型的集合还是纯值的集合
        //2 需要的时候如何找,我们发现后面需要根据收的人来查找会话, 所以一定不能 list 这种不区分的集合,否则需要挨个遍历,更适合用 key-value 这样的键值对集合比如 map
        // 我们保存的时候需要一个 key,这个 key 必须是唯一的, 而且每个会话对应的 key 还不能变,因为 key 变了就找不到数据了
        // 我们每个用户都有用户名或者 id,就像 QQ 的 QQ 号是唯一的,我们可以使用这个唯一的标识来作为 key,如何知道当前的会话对应的是什么 key
        //websocket中可以通过请求路径来传递一个唯一标识过来 比如 websocket 的连接地址是 /websocket 可以在后加/12345 来声明这是 12345
        String uuid = (String) session.getAttributes().get("name");
        allClients.put(uuid, session);//根据之前拦截保存的唯一表示来保存会话
        //其他操作,比如像 QQ 登录后获取还没接收的消息,但是这是具体的业务了
        context.publishEvent(DeviceEvent.sendOpen(uuid));
    }

    /**
     * 处理文本消息的,相当于你在给你的女神发送消息
     *
     * @param session 会话,发送数据到服务器的会话是谁
     * @param message 消息的内容,在聊天的情况下,只有发送者和接收者不一样,其他的的东西都是一样的,张三给李四发消息和王五给赵六发送消息是一样,所以方法就这一个,那额如何区分接收者,肯定需要在消息的内容中来处理
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        //客户端发送消息给服务端,服务端到底如何处理这个消息,这个也是具体的业务
        //我们处理一下消息, 我们现在要做的事情是实现一个聊天,首先发送消息的人会将消息发送到服务器,当前方法就会执行,我们需要在方法内部找到这个消息的目标,然后转过去
        //如果对方不在线, 我们可以给发送者返回一句对方不在线, 消息也可以保存起来
        //我们约定.发送的数据是 json格式的数据,其中有一个字段代表接收者的标识,比如 toUser 代表接收者的标识,  msg 代表消息的内容
        String content = message.getPayload();//这个获取到的数据发送过来的内容,注意这个内容并不是单纯的只是聊天的内容
        Map<String, String> map = objectMapper.readValue(content, Map.class);
        String toUser = map.get("toUser");//获取接收者
        String msg = map.get("msg");//获取消息的内容
        //要想给对方发送数据,必须找到对方的会话,也就是说我们要从保存会话的地方找到会话
        WebSocketSession toUSerSession = allClients.get(toUser);//根据用户的标识找到用户
        //如果用户在线,就发送内容
        if (toUSerSession != null && toUSerSession.isOpen()) {
//            toUSerSession.sendMessage(new TextMessage("收到来自:" + session.getAttributes().get("name") + "的消息: -=====>  " + msg));
            toUSerSession.sendMessage(new TextMessage(msg));
        }else{
            //对方不在线
            session.sendMessage(new TextMessage("对方不在线"));
        }
    }

    /**
     * 客户端和服务器连接关闭了执行当前方法,相当于你退出了 QQ,这时候实际上要关闭会话连接,因为你已经断开,如果我不删除保存的数据的话,我能够查询继续给你发送消息,但是实际上是发布过去的
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        if (session != null) {
            String uuid = (String) session.getAttributes().get("name");
            allClients.remove(uuid);
            context.publishEvent(DeviceEvent.sendClose(uuid));
        }
        super.afterConnectionClosed(session, status);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if (session != null) {
            String uuid = (String) session.getAttributes().get("name");
            allClients.remove(uuid);
            context.publishEvent(DeviceEvent.sendClose(uuid));
        }

        super.handleTransportError(session, exception);
    }

    public static int sendControl(String uuid, String command) throws IOException {
        WebSocketSession session = allClients.get(uuid);
        if (session != null && session.isOpen()) {
            session.sendMessage(new TextMessage(command));
            return 1;
        }
        return 0;
    }

   public static WebSocketSession getClient(String uuid) {
       return allClients.get(uuid);
    }
}
